﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Runtime.Serialization;
using TotalLMS.Util;

namespace TotalLMS.Datamodels.cmi
{
    //TODO: This class is not implemented completly, just partially
    public class CMIRequest : ISerializable
    {
        // The original LMSGetValue() or LMSSetValue() request un-tokenized
        private String request;

        // The model token of the request
        private String model;

        // The Base Category of the request
        private String baseCategory;

        // A collection of Sub-Categories of the request (if any)
        private int numSub = 10;
        private String[] subcategory;

        // The number of Sub-Categories collected from the request (if any)
        private int numOfSubCategories = 0;

        // A running total of the number of Sub-Categories returned (if any)
        private int numOfSubCatReturned = 0;

        // A collection of array indices collected from the request (if any)
        private int numInd = 10;
        private int[] index;

        // The number of array indices collected from the request (if any)
        private int numOfInd = 0;

        // A running total of the number of array indices returned (if any)
        private int numOfIndReturned = 0;

        // The Element of the request
        private String element;

        // The set value of the request (if LMSSetValue() request)
        private String setValue;

        // The total number of tokens found in the request.  If the request
        // is an LMSSetValue() the set value is not counted as a token
        private int totalNumberOfTokens = 0;

        // A running total of the number of tokens requested.  Initialized to
        // 1, because of the model.  Design does not ask for the model.
        private int tokensRequested = 1;

        // A running total of the number of tokes processed
        private int tokensProcessed = 0;

        // bool value indicating if this Request is for an LMSGetValue()
        private bool getRequest;

        // bool value indicating if this Request is for an LMSSetValue()
        private bool setRequest;

        // This attribute keeps track of the types of tokens processed
        // and keeps them in order for later use
        // 1 - model
        // 2 - base category
        // 3 - sub category
        // 4 - array index
        // 5 - element
        // 6 - value to use for set
        // cmi.core.student_id -- 1,2,5
        // cmi.student_data.tries.n.score.raw -- 1,2,3,4,3,5
        // Used to indicate what the token is
        private int numPat = 10;
        private int[] pattern;

        static private int MODEL = 1;
        static private int BASE_CATEGORY = 2;
        static private int SUB_CATEGORY = 3;
        static private int ARRAY_INDEX = 4;
        static private int ELEMENT = 5;
        static private int VALUE = 6;

        // initialize to 2 because the first two tokens would have
        // been processed before this value is used.
        static int pattern_counter = 2;

        // static method to increment the pattern_counter
        static void incrementPatternCount()
        {
            pattern_counter++;
        }

        // static method to reset the pattern_counter
        static void resetPatternCounter()
        {
            pattern_counter = 2;
        }


        /****************************************************************************
         **
         ** Method:  Constructor
         ** Input:   String theRequest - the request from the AU
         **          bool getRequestFlag - flag indicating whether or not this
         **                                   request is for an LMSGetValue()
         ** Output:  none
         **
         ** Description: This constructor is responsible for creating the request
         **        object.  The constructor takes the input request
         **        (i.e. cmi.core.student_id) and parses the string into tokens.
         **
         ***************************************************************************/
        public CMIRequest(String theRequest,
                          bool getRequestFlag)
   {
      if ( DebugIndicator.ON )
      {
         if ( getRequestFlag )
         {
             Console.WriteLine("Building CMIRequest for a LMSGetValue(" + theRequest + ") ");
         }
         else
         {
            Console.WriteLine("Building CMIRequest for a LMSSetValue(" + theRequest + ") ");
         }
      }

      // Set up initial values
      request = theRequest;
      model = new String("");
      baseCategory = new String("");
      element = new String("");
      setValue = new String("");

      getRequest = getRequestFlag;


      //***********************
      // Set up index array
      //**********************
      index = new Integer[numInd];

      // assign a value to each array element and print
      for ( int i = 0; i < index.length; i++ )
      {
         index[i] = new Integer(-1);
      }

      //***********************
      // Set up pattern array
      //**********************
      pattern = new int[numPat];

      // assign a value to each array element and print
      for ( int j = 0; j < pattern.Length; j++ )
      {
         pattern[j] = new Integer(-1);
      }

      //**************************
      // Set up subcategory array
      //**************************
      subcategory = new String[numSub];

      // assign a value to each array element and print
      for ( int z = 0; z < subcategory.Length; z++ )
      {
         subcategory[z] = new String("-1");
      }

      // used to hold the set value on an LMSSetValue()
      String theSetValue = new String("");

      // used to hold request
      String theNewRequest = new String("");

      // Check to see if this is a LMSGetValue() request
      if ( getRequest )
      {
         setRequest = false;
         parseGetRequest(theRequest);
      }
      else
      {
         setRequest = true;
         // Break up the request into into tokens
         StringTokenizer setTok = new StringTokenizer(theRequest, ",", false);
         int numTokensForSet =  setTok.countTokens();
         int numTokensProcessed = 0;

         theNewRequest = setTok.nextToken();
         numTokensProcessed++;

         if ( DebugIndicator.ON )
         {
            Console.WriteLine("Request: [" + theNewRequest + "]");
         }

         if ( numTokensForSet == 1 )
         {
            theSetValue = new String("");
         }
         else
         {

            //Loop to build the value for a LMSSetValue()
            while (numTokensProcessed < numTokensForSet)
            {
               theSetValue += setTok.nextToken();
               numTokensProcessed++;

               if (numTokensProcessed != numTokensForSet)
               {
                   theSetValue += ",";
               }
            }

            if ( DebugIndicator.ON )
            {
               Console.WriteLine("Set Value: [" + theSetValue + "]");
            }
         }

         totalNumberOfTokens++;
         parseSetRequest(theNewRequest,theSetValue);
      }

      if ( DebugIndicator.ON )
      {
         this.showRequest();
      }

   } // end of CMIRequest()

        /***************************************************************************
         **
         ** Method:  getModel()
         ** Input:   none
         ** Output:  String - data model used
         **
         ** Description:  Indicates the data model used
         **
         ***************************************************************************/
        public String getModel()
        {
            return model;
        }


        /***************************************************************************
         **
         ** Method:  isForASetRequest()
         ** Input:   none
         ** Output:  bool setRequest
         **
         ** Description:  Indicates whether or not the current request is for
         **               an LMSSetValue() request
         **
         ***************************************************************************/
        public bool isForASetRequest()
        {
            return setRequest;
        }

        /***************************************************************************
         **
         ** Method:  isAKeywordRequest()
         ** Input:   none
         ** Output:  bool result
         **
         ** Description:  Indicates whether or not the current request is for
         **               keyword request--> _children, _count or _version
         **
         ***************************************************************************/
        public bool isAKeywordRequest()
        {
            bool result = false;
            if ((isAChildrenRequest()) ||
                 (isACountRequest()) ||
                 (isAVersionRequest()))
            {
                result = true;
            }
            return result;
        }

        /***************************************************************************
         **
         ** Method:  isAChildrenRequest()
         ** Input:   none
         ** Output:  bool result
         **
         ** Description:  Indicates whether or not the current request is for
         **               an _children request
         **
         ***************************************************************************/
        public bool isAChildrenRequest()
        {
            bool result = false;
            if (element.equals("_children"))
            {
                result = true;
            }
            return result;
        }

        /***************************************************************************
         **
         ** Method:  isACountRequest()
         ** Input:   none
         ** Output:  bool result
         **
         ** Description:  Indicates whether or not the current request is for
         **               an _count request
         **
         ***************************************************************************/
        public bool isACountRequest()
        {
            bool result = false;
            if (element.equals("_count"))
            {
                result = true;
            }
            return result;
        }

        /***************************************************************************
         **
         ** Method:  isAVersionRequest()
         ** Input:   none
         ** Output:  bool result
         **
         ** Description:  Indicates whether or not the current request is for
         **               an _version request
         **
         ***************************************************************************/
        public bool isAVersionRequest()
        {
            bool result = false;
            if (element.equals("_version"))
            {
                result = true;
            }
            return result;
        }
        /***************************************************************************
         **
         ** Method:  getRequest
         ** Input:   none
         ** Output:  String request - Original unparsed request
         **
         ** Description:  This method returns the original unparsed request
         **
         ***************************************************************************/
        public String getRequest()
        {
            return request;
        }

        /***************************************************************************
         **
         ** Method:  getBaseCategory
         ** Input:   none
         ** Output:  String baseCategory - the base category found in request
         **
         ** Description:  This method returns the Base Category
         **
         ***************************************************************************/
        public String getBaseCategory()
        {
            tokensRequested++;
            return baseCategory;
        }

        /***************************************************************************
         **
         ** Method:  getValue
         ** Input:   none
         ** Output:  String value - the set value
         **
         ** Description:  This method returns the Value to be used for setting.
         **
         ***************************************************************************/
        public String getValue()
        {
            return setValue;
        }

        /***************************************************************************
         **
         ** Method:  getElement
         ** Input:   none
         ** Output:  String value - the element
         **
         ** Description:  This method returns the Element of the request
         **
         ***************************************************************************/
        public String getElement()
        {
            return element;
        }

        /***************************************************************************
         **
         ** Method:  getTotalNumTokens
         ** Input:   none
         ** Output:  String totalNumberOfTokens - The total number of tokens
         **          in the request
         **
         ** Description:  This method returns the Total Number of Tokens
         **
         ***************************************************************************/
        public int getTotalNumTokens()
        {
            return totalNumberOfTokens;
        }

        /***************************************************************************
         **
         ** Method:  getNumSubCat()
         ** Input:   none
         ** Output:  int - the number of subcategories
         **
         ** Description:  This method returns the number of sub categories
         **               in the request
         **
         ***************************************************************************/
        public int getNumSubCat()
        {
            return numOfSubCategories;
        }

        /***************************************************************************
          **
          ** Method:  getNumIndices()
          ** Input:   none
          ** Output:  int - the number of indicies
          **
          ** Description:  This method returns the number of indices found
          **               in the request
          **
          ***************************************************************************/
        public int getNumIndices()
        {
            return numOfInd;
        }

        /***************************************************************************
         **
         ** Method:  getIndex()
         ** Input:   none
         ** Output:  Integer - the position in the array to find the index
         **
         ** Description:  This method returns the index found at the input position
         **
         ***************************************************************************/
        public Integer getIndex(int position)
        {
            return index[position];
        }

        /***************************************************************************
         **
         ** Method:  getSubCategory()
         ** Input:   int - which subcategory
         ** Output:  String - the subcategory
         **
         ** Description:  This method returns the subcategory found at the
         **               input position
         **
         ***************************************************************************/
        public String getSubCategory(int position)
        {
            String subCat = new String("-1");

            if ((position >= 0) && (position < 10))
            {
                subCat = subcategory[position];
            }

            return subCat;
        }

        /***************************************************************************
         **
         ** Method:  parseGetRequest
         ** Input:   StringTokenizer stk - String Tokenizer holding request
         ** Output:  none
         **
         ** Description:  This method performs the necessary steps to parse
         **               an LMSGetValue() request.
         **
         ***************************************************************************/
        private void parseGetRequest(String theRequest)
        {
            // Break up the request into tokens
            StringTokenizer stk = new StringTokenizer(theRequest, ".", false);

            // set the total number of tokens in the request
            totalNumberOfTokens = totalNumberOfTokens + stk.countTokens();

            // First token is the model
            model = stk.nextToken();
            tokensProcessed++;

            pattern[0] = (new Integer(MODEL));

            // Second token is the baseCategory
            baseCategory = stk.nextToken();
            tokensProcessed++;

            pattern[1] = (new Integer(BASE_CATEGORY));

            if (stk.hasMoreTokens())
            {
                // The only thing that can follow a category is
                // either another category (subcategory), array index,
                // or an element.

                // declare a bool to be used to indicate when processing is done
                bool done = false;

                // Loop until done
                while (!done)
                {
                    // Check to see if we are on the last token (ELEMENT)
                    if (onLastToken())
                    {
                        // Last token to be processed, this is assigned
                        // to the element attribute
                        String temp = stk.nextToken();

                        try
                        {
                            // Try converting the token to an Integer. If this
                            // is successfull, then we are dealing with an array index
                            Integer tmpInt = new Integer(temp);
                            int tmpIndex = findNextIndexLoc();

                            // Store off the array index
                            index[tmpIndex] = tmpInt;

                            // Increment the number of indicies
                            numOfInd++;

                            int tmpIndex1 = findNextPatternLoc();

                            // Store off the array index pattern number
                            pattern[tmpIndex1] = (new Integer(ARRAY_INDEX));

                            // The last subcategory processed is the element.
                            // Use the numOfSubCategores as the base index.  Need
                            // to subtract 1 to get to the correct index.  Currently
                            // the only data model element that falls into this
                            // category is cmi.objectives.n.status.n
                            element = subcategory[numOfSubCategories - 1];

                            tmpIndex1 = findNextPatternLoc();
                            pattern[tmpIndex1] = (new Integer(ELEMENT));


                        }
                        catch (NumberFormatException nfe)
                        {
                            element = temp;
                            int tmpIndex = findNextPatternLoc();

                            pattern[tmpIndex] = (new Integer(ELEMENT));
                        }

                        // increment the tokens processed
                        tokensProcessed++;

                        done = true;
                    }  // end of if onLastToken()
                    else
                    {
                        // Check to see if next token is an array index
                        // determineNextToken() will set index up if the element
                        // being processed is an array index.
                        //
                        // cmi.objectives.n.id (index will be set to n on
                        // return from method
                        //
                        // cmi.core.score.raw (subcategory1 or subcategory2
                        // will be set to score on return from method
                        determineNextToken(stk);
                    }
                } // end of while
            }
            else
            {
                // Request only had two tokens: (i.e. "cmi.comments" or "cmi.suspend_data")
                // Need to set the element attribute to the base category
                element = baseCategory;
            }
        } // end parseGetRequest


        /***************************************************************************
        **
        ** Method:  parseSetRequest
        ** Input:   StringTokenizer stk - String Tokenizer holding request
        ** Output:  none
        **
        ** Description:  This method performs the necessary steps to parse
        **               an LMSSetValue() request.
        **
        ***************************************************************************/
        private void parseSetRequest(String theRequest,
                                     String theSetValue)
   {
      if ( DebugIndicator.ON )
      {
         Console.WriteLine("In CMIRequest::parseSetRequest");
      }

      // Break up the request into tokens
      StringTokenizer stk = new StringTokenizer(theRequest, ".", false);

      // set the total number of tokens in the request
      totalNumberOfTokens = totalNumberOfTokens + stk.countTokens();

      // First token is the model
      model = stk.nextToken();
      tokensProcessed++;

      pattern[0] = (new Integer(MODEL));

      // Second token is the baseCategory
      baseCategory = stk.nextToken();
      tokensProcessed++;

      pattern[1] = (new Integer(BASE_CATEGORY));

      if ( stk.hasMoreTokens() )
      {
         // The only thing that can follow a category is
         // either another category (subcategory), array index,
         // or an element.

         // declare a bool to be used to indicate when processing is done
         bool done=false;

         // Loop until done
         while ( !done )
         {
            // Check to see if we are on the last token (ELEMENT)
            if ( onLastToken() )
            {
               // Last token to be processed, this is assigned
               // to the element attribute
               setValue = theSetValue;
               tokensProcessed++;

               int tmpIndex = findNextPatternLoc();

               pattern[tmpIndex] = (new Integer(VALUE));
               done = true;
            }  // end of if onLastToken()
            else if ( (totalNumberOfTokens - tokensProcessed) == 2 )
            {
               element = stk.nextToken();
               tokensProcessed++;
               int tmpIndex = findNextPatternLoc();

               pattern[tmpIndex] = (new Integer(ELEMENT));
            }
            else
            {
               // Check to see if next token is an array index
               // determineNextToken() will set index up if the element
               // being processed is an array index.
               //
               // cmi.objectives.n.id (index will be set to n on
               // return from method
               //
               // cmi.core.score.raw (subcategory1 or subcategory2
               // will be set to score on return from method
               determineNextToken(stk);
            }
         } // end of while
      }
      else
      {
         // Last token to be processed, this is assigned
         // to the element attribute
         setValue = theSetValue;
         tokensProcessed++;
         int tmpIndex = findNextPatternLoc();

         pattern[tmpIndex] = (new Integer(VALUE));
      }

   } // end parseGetRequest

        /*****************************************************************************
         **
         ** Method:  determineNextToken
         ** Input:   StringTokenizer stk - String Tokenizer holding the request
         ** Output:  none
         **
         ** Description:  This method is used to determine the next token.  The
         **               method first attempts to see if the next token is an
         **               array element.  If so, the method stores off the array
         **               element.  If the next element is a subcategory,
         **               the method stores off the subcategory
         **
         ***************************************************************************/
        private void determineNextToken(StringTokenizer stk)
        {
            // Get the next token from the String Tokenizer
            String temp = stk.nextToken();

            // increment the number of tokens processed
            tokensProcessed++;

            // Check to see if the token is an array index or a
            // subcategory
            try
            {
                // Try converting the token to an Integer. If this
                // is successfull, then we are dealing with an array index
                Integer tmpInt = new Integer(temp);

                int tmpIndex = findNextIndexLoc();

                // Store off the array index
                index[tmpIndex] = tmpInt;

                // Increment the number of indicies
                numOfInd++;

                int tmpIndex1 = findNextPatternLoc();

                // Store off the array index pattern number
                pattern[tmpIndex1] = (new Integer(ARRAY_INDEX));
            }
            catch (NumberFormatException nfe)
            {
                // Token could not be converted to a number
                // Dealing with a subcategory

                int tmpIndex = findNextSubLoc();

                // store off the subcategory
                subcategory[tmpIndex] = temp;

                // Increment the number of subcategories
                numOfSubCategories++;

                int tmpIndex1 = findNextPatternLoc();

                // Store off the subcategory pattern number
                pattern[tmpIndex1] = (new Integer(SUB_CATEGORY));
            }
        }  // end of determineNextToken()


        /****************************************************************************
         **
         ** Method:  onLastToken
         ** Input:   none
         ** Output:  bool result indicating whether or not the code is on the
         **          last token
         **
         ** Description:  This method determines if we are on the last token
         **
         ***************************************************************************/
        private bool onLastToken()
        {
            bool rtrnFlag = false;

            if ((totalNumberOfTokens - tokensProcessed) == 1)
            {
                rtrnFlag = true;
            }
            return rtrnFlag;
        } // end of onLastToken()


        /***************************************************************************
        **
        ** Method:  getNextToken
        ** Input:   none
        ** Output:  String - next available token
        **
        ** Description: This method uses the pattern collection to determine
        **              the next (in order from original request) availabe
        **              token.
        **
        ***************************************************************************/
        public String getNextToken()
   {
      // Value to return
      String rtrnValue = new String("");

      try
      {
         Integer tmpPat = pattern[pattern_counter];
         // Check pattern counter to see what the next token will be
         int tmpPattern = tmpPat.intValue();

         // Determine the appropriate value to return based on the pattern
         if ( tmpPattern == SUB_CATEGORY )
         {
            try
            {
               // Dealing with sub categories
               rtrnValue = subcategory[numOfSubCatReturned];
               numOfSubCatReturned++;
            }
            catch ( IndexOutOfBoundsException ioobe )
            {
               Console.WriteLine(ioobe);
            }
         }
         else if ( tmpPattern == ARRAY_INDEX )
         {
            try
            {
               Integer tmpIndex = index[numOfIndReturned];
               numOfIndReturned++;
               // Dealing with array indexes
               rtrnValue = tmpIndex.toString();

            }
            catch ( IndexOutOfBoundsException ioobe )
            {
               Console.WriteLine(ioobe);
            }
         }
         else if ( tmpPattern == ELEMENT )
         {
            // Dealing with elements
            rtrnValue = element;

            // on last token (element) reset static pattern
            // count
            resetPatternCounter();
         }
         else if ( tmpPattern == VALUE )
         {
            // Dealing with set values
            rtrnValue = setValue;
         }

         // If the pattern that is being processed is not an element
         // increment the static pattern count
         if ( tmpPattern != ELEMENT )
         {
            incrementPatternCount();
         }
         tokensRequested++;
      }
      catch ( IndexOutOfBoundsException ioobe )
      {
         Console.WriteLine(ioobe);
      }

      return rtrnValue;
   }  // end of getNextToken()

        /***************************************************************************
        **
        ** Method:  hasMoreTokensToProcess()
        ** Input:   none
        ** Output:  bool returnValue - indicates how many tokens are left
        **          to process
        **
        ** Description:  Determines if there are more tokens left to process
        **
        ***************************************************************************/
        public bool hasMoreTokensToProcess()
        {
            bool rtrnFlag = true;

            // If dealing with LMSSetValue() request don't count the setValue
            if (setRequest)
            {
                if (((totalNumberOfTokens - 1) - tokensRequested) == 0)
                {
                    rtrnFlag = false;
                }
            }
            else
            {
                if ((totalNumberOfTokens - tokensRequested) == 0)
                {
                    rtrnFlag = false;
                }
            }

            return rtrnFlag;
        } // end of hasMoreTokensToProcess()

        /***************************************************************************
         **
         ** Method:  done
         ** Input:   none
         ** Output:  none
         **
         ** Description:  This method is invoked when the processing of a request
         **               is finished.  It resets counters used in processing
         **
         ***************************************************************************/
        public void done()
        {
            resetPatternCounter();
            tokensRequested = 1;
            numOfSubCatReturned = 0;
            numOfIndReturned = 0;
            return;
        }

        /***************************************************************************
         **
         ** Method:  findNextPatternLoc
         ** Input:   none
         ** Output:  int - The next available position in the pattern array
         **
         ** Description:  Determines the next available position in the pattern
         **               array and returns it to the caller
         **
         ***************************************************************************/
        private int findNextPatternLoc()
        {
            int result = -1;
            int value = -1;

            for (int i = 0; i <= numPat; i++)
            {
                if (value == pattern[i].intValue())
                {
                    result = i;
                    break;
                }
            }

            return result;
        }

        /***************************************************************************
         **
         ** Method:  findNexIndexLoc
         ** Input:   none
         ** Output:  int - The next available position in the index array
         **
         ** Description:  Determines the next available position in the index
         **               array and returns it to the caller
         **
         ***************************************************************************/
        private int findNextIndexLoc()
        {
            int result = -1;
            int value = -1;

            for (int i = 0; i <= numInd; i++)
            {
                if (value == index[i].intValue())
                {
                    result = i;
                    break;
                }
            }

            return result;
        }

        /***************************************************************************
         **
         ** Method:  findNexSubLoc
         ** Input:   none
         ** Output:  int - The next available position in the subcategory array
         **
         ** Description:  Determines the next available position in the subcategory
         **               array and returns it to the caller
         **
         ***************************************************************************/
        private int findNextSubLoc()
        {
            int result = -1;
            String tmpString = new String("-1");
            for (int i = 0; i <= numSub; i++)
            {
                if (subcategory[i].equalsIgnoreCase(tmpString))
                {
                    result = i;
                    break;
                }
            }

            return result;
        }

        /***************************************************************************
         **
         ** Method:  showRequest()
         ** Input:   none
         ** Output:  none
         **
         ** Description:  Sends important information about the request to System.out
         **
         **
         ***************************************************************************/
        public void showRequest()
   {
      if ( DebugIndicator.ON )
      {
          Console.WriteLine("Base Category: " + baseCategory);
          Console.WriteLine("Element: " + element);
          Console.WriteLine("Model: " + model);
          Console.WriteLine("Number of Indices: " + numOfInd);
          Console.WriteLine("Number of SubCategories: " + numOfSubCategories);

          for ( int i=0; i < numOfSubCategories; i++)
          {
             Console.WriteLine("Subcategory[" + i + "]: " + subcategory[i]);
          }

          for ( int j=0; j < numOfInd; j++)
          {
             Console.WriteLine("Index[" + j + "]: " + index[j]);
          }

          for ( int x=0; x < numPat; x++)
          {
             if ( pattern[x].intValue() > -1 )
             {
                Console.WriteLine("Pattern[" + x + "]: " + pattern[x]);
             }
          }

          if ( this.isForASetRequest() )
          {
             Console.WriteLine("Set Value: " + setValue);
          }
      }

   }



        #region ISerializable Members
        //TODO: how to implement this interface method
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
